import requests
from bs4 import BeautifulSoup
import time
import csv
import random

# 基础URL和请求头（包含排序参数）
base_url = "https://movie.douban.com/subject/34780991/reviews"
params = {
    "sort": "hotest",  # 明确排序方式
    "start": 0  # 分页起点
}
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
    'Referer': 'https://movie.douban.com/'
}

# 存储数据的列表
reviews = []


def get_reviews(start):
    """获取单页影评"""
    try:
        # 动态构造请求参数
        current_params = params.copy()
        current_params["start"] = start

        # 发送请求（包含重试逻辑）
        response = requests.get(
            url=base_url,
            params=current_params,
            headers=headers,
            timeout=10
        )
        response.raise_for_status()

        # 验证是否返回真实数据
        if "检测到有异常请求" in response.text:
            raise Exception("触发反爬机制")

        soup = BeautifulSoup(response.text, 'html.parser')

        # 解析逻辑（根据2023年最新页面结构调整）
        items = soup.find_all('div', class_='review-item')
        if not items:
            print("未找到影评条目，可能页面结构已更新")
            return False

        for item in items:
            # 标题解析增强健壮性
            title_tag = item.find('a', class_='title')
            title = title_tag.text.strip() if title_tag else "无标题"

            # 作者信息
            author_tag = item.find('a', class_='name')
            author = author_tag.text.strip() if author_tag else "匿名用户"

            # 评分处理（兼容无评分情况）
            rating_tag = item.find('span', class_='main-title-rating')
            rating = rating_tag['title'] if rating_tag else "未评分"

            # 发布时间
            time_tag = item.find('span', class_='main-meta')
            time_published = time_tag.text.strip() if time_tag else "时间未知"

            # 内容提取（处理展开前的内容）
            content_tag = item.find('div', class_='short-content')
            content = content_tag.text.strip().replace('\n', ' ') if content_tag else "无内容"

            reviews.append({
                '标题': title,
                '作者': author,
                '评分': rating,
                '发布时间': time_published,
                '内容': content[:200]  # 防止过长
            })

        # 检查下一页（改进判断逻辑）
        next_link = soup.find('a', string='后页')
        return bool(next_link and not next_link.has_attr('disabled'))

    except Exception as e:
        print(f"请求失败: {str(e)[:50]}")  # 截取部分错误信息
        return False


# 爬取控制（自动检测最大页数）
current_start = 0
max_retry = 3
while True:
    print(f"正在爬取 start={current_start}...")

    success = False
    for _ in range(max_retry):
        if get_reviews(current_start):
            success = True
            break
        time.sleep(random.uniform(5, 10))  # 随机延迟

    if not success:
        print("连续失败，终止爬取")
        break

    # 自动翻页（步长20）
    current_start += 20

    # 随机延迟策略
    time.sleep(random.uniform(8, 15))  # 更安全的间隔

    # 安全限制（示例最多爬取1000条）
    if current_start >= 1000:
        print("达到安全限制")
        break

# 改进的存储方式（处理特殊字符）
if reviews:
    with open('douban_hot_reviews.csv', 'w', newline='', encoding='utf-8-sig') as f:
        writer = csv.DictWriter(f, fieldnames=['标题', '作者', '评分', '发布时间', '内容'])
        writer.writeheader()
        for row in reviews:
            # 清洗数据：移除不可见字符
            cleaned_row = {k: v.replace('\x00', '') for k, v in row.items()}
            writer.writerow(cleaned_row)
    print(f"成功保存 {len(reviews)} 条影评")
else:
    print("无数据可保存")